
// ===============================================================================================================
// -*- C++ -*-
//
// Renderer.hpp - Declaration of the Renderer singleton class.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#ifndef __RENDERER_HPP__
#define __RENDERER_HPP__

// C++ std includes
#include <queue>
#include <string>
#include <hash_map>

// Local includes
#include <Common.hpp>
#include <Image.hpp>
#include <Texture.hpp>
#include <ShaderProgram.hpp>

///
/// Renderer -- Master class for all screen rendering operations.
/// It also manages hardware resources, such as textures and vertex buffers.
/// When the renderer is destroyed, thru the method Kill(), it also destroy all resources
/// generated by it, such as textures, buffers, etc.
///
/// The renderer is a singleton.
///
class Renderer {

protected:

	// Construction / Destruction:

	Renderer(void);

	virtual ~Renderer(void);

public:

	// Public Constants:

	///
	/// Flags passed to BeginScene() indicating wich buffer to clear out.
	///
	enum {

		// This is currently a OpenGL renderer, so we sync this constants.
		CLEAR_DEPTH_BUFFER   = GL_DEPTH_BUFFER_BIT,
		CLEAR_STENCIL_BUFFER = GL_STENCIL_BUFFER_BIT,
		CLEAR_COLOR_BUFFER   = GL_COLOR_BUFFER_BIT,
	};

	///
	/// Renderer initialization flags.
	///
	enum {

		INIT_FULLSCREEN = (1 << 0),
		INIT_WINDOWED   = (1 << 1),

		INIT_DOUBLE_BUFFERED = (1 << 2),
		INIT_DEPTH_BUFFER    = (1 << 3), /* Depth buffer is required only when rendering 3D geometry */
	};

	///
	/// Maximum string length for the PrintString() method.
	///
	enum {

		MAX_STRING_LEN = 2048
	};

public:

	// Public Data:

	// This variables are provided as public since they are frequently accessed outside the class.

	/* Read Only */ static int videoResolutionX; ///< Video resolution in the X direction. (in pixels)
	/* Read Only */ static int videoResolutionY; ///< Video resolution in the Y direction. (in pixels)

	/* Read Only */ static double FOV;   ///< Field Of View
	/* Read Only */ static double zFar;  ///< Maximum Z distance before clipping
	/* Read Only */ static double zNear; ///< Minimum Z distance before clipping

public:

	// Public Interface:

	/// Switch to full screen mode. No return to window mode after that.
	void SwitchToFullScreen(void) const;

	/// Begin a rendering pass. Must be called once at the begining of the rendering loop.
	void BeginScene(unsigned int flags);

	/// Flush the rendering pipeline and actualy draw the scene to the framebuffer.
	void EndScene(void);

	/// Set the current active camera.
	/// Do not delete the camera object after setting it!
	/// The camera is currently not reference countable.
	void SetCamera(const class Camera * cam);

	/// Return the current active camera.
	const class Camera * GetCamera(void) const;

	/// Create and register a 2D texture from an image object.
	/// If the texture has already been created previously, return it instead of creating again.
	/// You should call Release() on the texture once it is no longer needed.
	Texture * Create2DTextureFromImage(const Image * sourceImage);

	/// Create and register a 2D texture from an file.
	/// If the texture has already been created previously, return it instead of creating again.
	/// You should call Release() on the texture once it is no longer needed.
	Texture * Create2DTextureFromFile(const std::string & fileName);

	/// Releases cached textures from memory. (Textures that are only being referenced by the renderer)
	/// This will cause unused textures to be permanently deleted.
	void ReleaseCachedTextures(void);

	// 2D Rendering:

	/// Draw a 2D image in screen coordinates.
	void DrawImage(int x, int y, const Image * image);

	/// Draw a progress bar in the middle of the screen.
	/// \param [in] text: Text displayed under of the bar.
	/// \param [in] colors: colors[0] -> Text color;
	///						colors[1] -> Bar border color;
	///						colors[2] -> Bar background color;
	///						colors[3] -> Progress bar color;
	/// \param [in] prc: Amount of the bar to display. 0.0 -> 0% / 1.0 -> 100%
	void DrawProgressBar(const std::string & text, const unsigned int colors[], int width, int height, float prc);

	/// Fades the entire screem with the given color.
	void FadeScreen(unsigned int color);

	/// Prints a format string to the window.
	/// The length (in chars) of the string is limited by the constant MAX_STRING_LEN, including the '\0'.
	void PrintString(int x, int y, unsigned int color, const char * format, ...);

	// 3D Rendering:

	/// Send an MD5 Model to the rendering queue.
	/// The renderer apply textures and the given transformation matrix to the model defore drawing.
	void DrawDoomMD5Model(const class DoomMD5Model * model, const float * transform, const Texture * normalMap = 0, ShaderProgram * shader = 0);

	/// Renders the current game level and all objects associated with it.
	void DrawGameLevel(const class GameLevel * level, const Texture * normalMap = 0, ShaderProgram * shader = 0);

	/// Renders a billboard object, always facing the current camera.
	void DrawBillboard(const class Billboard * billboard, float animationTimer);

	/// Render an MD5 Model as the player weapon. That is, the object always follows
	/// and rotates by the current camera, simulating a weapon in the hands of the player.
	void DrawPlayerWeapon(const class DoomMD5Model * weapon, const float * transform);

	/// Render a 3D cube with textures applied to the interior of it.
	/// \sa SkyBox
	void DrawSkyBox(const class SkyBox * pBox);

public:

	// Singleton Related Methods:

	/// Initializes the renderer. Must be called once at program startup.
	static bool Initialize(int vidWidth, int vidHeight, unsigned int miscFlags, const std::string & windowTitle);

	/// Get the singleton unique instance.
	static Renderer * Instance(void);

	/// Destroy the renderer, i.e. delete the unique instance of this class.
	/// \note This function must be called before exiting in order to do a proper clean up.
	static void Kill(void);

private:

	// Private Methods:

	/// Resize the renderer window. Updates videoResolutionX and videoResolutionY.
	static void OnWindowResize(int w, int h);

private:

	// Private Data:

	static Renderer * singleton; ///< The unique instance of this class

	// NOTE: Be aware that the stdext::hash_map is not a cplusplus
	// standard by the time I wrote this code. It is available under MS compilers
	// and some GCC versions as well. Anyway, it can safely be replaced by the std::map class
	// without changing any code, I just decided to use it here since it is available on my compiler.
	typedef stdext::hash_map<const std::string, Texture *> TexturesTable;

	TexturesTable loadedTextures; ///< Cached texture objects
	const class Camera * currentCamera; ///< Active camera
	const class DoomMD5Model * playerWeapon; ///< We have to render the weapon at the end since I'am using a z-buffer trick...
	const float * weaponTransform; ///< Player weapon transformation matrix. Only a reference, do not delete after setting!

private:

	// All 2D rendering is done in a deferred base. ie: we only send the data to
	// be rendered when EndScene() is called. This way we switch to 2D projection only once.

	struct DeferredImage {

		int x, y;
		const Image * image; // Release when done!
	};

	typedef std::queue<const DeferredImage> ImagesQueue;

	struct DeferredString {

		int x, y;
		unsigned int color;
		char text[MAX_STRING_LEN];
	};

	typedef std::queue<const DeferredString> StringsQueue;

	// Billboards are also defered.
	typedef std::queue<const Billboard * > BillboardsQueue; // Release all when done!

	ImagesQueue deferredImages; ///< Cached images that will be drawn when EndScene() is next called
	StringsQueue deferredStrings; ///< Cached strings that will be drawn when EndScene() is next called
	BillboardsQueue deferredBillboards; ///< Cached billboards that will be drawn when EndScene() is next called

	unsigned int fadeColor;
	bool fadeScreen;
};

// ===============================================================================================================

///
/// SkyBox sides.
/// Each side use a different texture.
///
enum SkyBoxSides {

	SKY_BOX_BACK   = 0,
	SKY_BOX_FRONT  = 1,
	SKY_BOX_BOTTOM = 2,
	SKY_BOX_TOP    = 3,
	SKY_BOX_LEFT   = 4,
	SKY_BOX_RIGHT  = 5,

	SKY_BOX_NUM_SIDES = 6 // Enumerator max range
};

///
/// SkyBox helper class.
/// Uses a display list under OpenGL.
/// \sa SkyBoxSides
///
class SkyBox {

public:

	friend class Renderer;

	SkyBox(float x, float y, float z, float width, float height, float length, Texture * const skyBoxTextures[SKY_BOX_NUM_SIDES]);

	~SkyBox(void);

private:

	GLuint displayListID;
};

#endif // __RENDERER_HPP__